package org.springframework.security.web;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;

import java.io.IOException;

/**
 * @author Dillon
 * @date 2024/6/30
 * @slogan 致敬大师 致敬未来的你
 * @desc springSecurity 默认的重定向策略
 */
public class DefaultRedirectStrategy implements RedirectStrategy {

	/**
	 * 日志记录
	 */
	protected final Log logger = LogFactory.getLog(getClass());

	private boolean contextRelative;

	/**
	 * 重定向请求code码
	 */
	private HttpStatus statusCode = HttpStatus.FOUND;

	/**
	 * 执行重定向请求
	 *
	 * @param request  请求对象
	 * @param response 响应对象
	 * @param url      请求URL
	 * @throws IOException 执行异常
	 */
	@Override
	public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
		// 获取url 拼接上下文
		String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
		// 加密重定向路径
		redirectUrl = response.encodeRedirectURL(redirectUrl);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(LogMessage.format("请求重定向到 %s", redirectUrl));
		}
		// 执行重定向
		if (this.statusCode == HttpStatus.FOUND) {
			response.sendRedirect(redirectUrl);
		} else {
			response.setHeader(HttpHeaders.LOCATION, redirectUrl);
			response.setStatus(this.statusCode.value());
			response.getWriter().flush();
		}
	}

	/**
	 * 重新计算上下文URL
	 *
	 * @param contextPath 上下文配置路径
	 * @param url         重定向URL
	 * @return 计算后的url
	 */
	protected String calculateRedirectUrl(String contextPath, String url) {
		// 如果不是绝对路径 且不需要上下文 则直接返回URL (重定向URL为绝对路径)
		if (!UrlUtils.isAbsoluteUrl(url)) {
			if (isContextRelative()) {
				return url;
			}
			// 如果需要上下文，则添加url
			return contextPath + url;
		}
		// 如果是绝对路径 但不需要上下文 直接返回url
		if (!isContextRelative()) {
			return url;
		}
		// 验证
		Assert.isTrue(url.contains(contextPath), "绝对路径中已存在上下文路径.");
		// 将上下文拼接到url中并返回
		url = url.substring(url.lastIndexOf("://") + 3);
		url = url.substring(url.indexOf(contextPath) + contextPath.length());
		if (url.length() > 1 && url.charAt(0) == '/') {
			url = url.substring(1);
		}
		return url;
	}

	/**
	 * 设置重定向不添加上下文配置
	 * 如果为真，则重定向时减去上下文
	 *
	 * @param useRelativeContext 设置值
	 */
	public void setContextRelative(boolean useRelativeContext) {
		this.contextRelative = useRelativeContext;
	}

	/**
	 * 获取重定向不添加上下文配置
	 *
	 * @return 重定向上下文关系配置
	 */
	protected boolean isContextRelative() {
		return this.contextRelative;
	}

	/**
	 * 设置重定向Code码
	 *
	 * @param statusCode 重定向code码
	 */
	public void setStatusCode(HttpStatus statusCode) {
		Assert.notNull(statusCode, "statusCode cannot be null");
		this.statusCode = statusCode;
	}

}
